
package com.tools.cleanmaster.utils;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.TypedValue;
import android.view.View;

import com.tools.cleanmaster.CleanApplication;
import com.tools.cleanmaster.common.MissedAPI;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class BitmapUtils {
    private static final boolean LOGV = FeatureConfig.DEBUG_LOG;

    private static final String TAG = "BitmapUtils";

    // -------------------------------------------------------------------------

    public static Bitmap createSnapshot(View v, Float scale) {
        Bitmap result = null;

        if (v.getWidth() > 0 && v.getHeight() > 0) {
            try {
                final int bmpWidth = (int) (v.getWidth() * scale);
                final int bmpHeight = (int) (v.getHeight() * scale);

                if (LOGV)
                    LogHelper.i(TAG, "w=" + bmpWidth + ", h=" + bmpHeight + ", s=" + scale);

                result = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);

                Canvas bmpCanvas = new Canvas(result);
                bmpCanvas.translate(-v.getScrollX(), -v.getScrollY());
                bmpCanvas.scale(scale, scale);

                v.cancelLongPress();
                v.requestFocus();
                v.draw(bmpCanvas);
            } catch (OutOfMemoryError e) {
                if (LOGV)
                    LogHelper.e(TAG, "OOM~~", e);
            }
        }

        return result;
    }

    // -------------------------------------------------------------------------

    /**
     * do not preserve ratio zoomed image is exactly w * h pixels
     */
    public static final int ZOOM_FLAGS_NORMAL = 0;

    /**
     * zoom the image into largest with ratio, fit into w/h, no crop zoomed
     * image would be smaller than w * h pixels, one of the edge will exactly as
     * w or h
     */
    public static final int ZOOM_FLAGS_FIT = 1;

    /**
     * zoom the image to width to 'w', keep ratio, height might be cropped to
     * fit target ratio zoomed image width = w, height will be determined by
     * source ratio
     */
    public static final int ZOOM_FLAGS_FILL_WIDTH = 2;

    /**
     * zoom the image to height to 'h', keep ratio, width might be cropped to
     * fit target ratio zoomed image height = h, width will be determined by
     * source ratio
     */
    public static final int ZOOM_FLAGS_FILL_HEIGHT = 3;

    /**
     * zoom the image into largest with ratio, fit into w/h, one of the
     * width/height might be cropped to keep the other side largest zoomed image
     * will be as large as possible to ensure (width == w || height == h), the
     * other side is determined by ratio
     */
    public static final int ZOOM_FLAGS_FILL = 4;

    static boolean isZero(float z) {
        return z > -0.0001 && z < 0.0001;
    }

    private static volatile Matrix sScaleMatrix;

    public static Bitmap zoomBitmap(Bitmap bitmap, boolean recycle, float ratio) {
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        return zoomBitmap(bitmap, recycle, (int) (w * ratio), (int) (h * ratio), ZOOM_FLAGS_NORMAL);
    }

    public static Bitmap zoomBitmap(Bitmap bitmap, boolean recycle, int w, int h) {
        return zoomBitmap(bitmap, recycle, w, h, ZOOM_FLAGS_NORMAL);
    }

    public static Bitmap zoomBitmap(Bitmap bitmap, boolean recycle, int w, int h, int flags) {
        final int width = bitmap.getWidth();
        final int height = bitmap.getHeight();

        if (w == width && h == height) {
            if (LOGV)
                LogHelper.i(TAG, "Bitmap: " + bitmap + " is already required size: w=" + w + ", h="
                        + h);
            return bitmap;
        }

        final float ratioSrc = ((float) width) / height;
        final float ratioDst = ((float) w) / h;

        final int origFlags = flags;

        if (isZero(ratioSrc - ratioDst)) {
            flags = ZOOM_FLAGS_NORMAL;
        }

        if (ZOOM_FLAGS_FILL == flags) {
            if (ratioSrc > ratioDst) {
                flags = ZOOM_FLAGS_FILL_HEIGHT;
            } else {
                flags = ZOOM_FLAGS_FILL_WIDTH;
            }
        }

        float targetWidth = w;
        float targetHeight = h;

        switch (flags) {
            case ZOOM_FLAGS_FIT: {
                if (ratioSrc > ratioDst) {
                    // keep w
                    targetWidth = w;
                    targetHeight = w / ratioSrc;
                } else {
                    // keep h
                    targetWidth = h * ratioSrc;
                    targetHeight = h;
                }
                break;
            }
            case ZOOM_FLAGS_FILL_WIDTH: {
                // keep w
                targetWidth = w;
                targetHeight = w / ratioSrc;
                break;
            }
            case ZOOM_FLAGS_FILL_HEIGHT: {
                // keep h
                targetWidth = h * ratioSrc;
                targetHeight = h;
                break;
            }
            case ZOOM_FLAGS_FILL: {
                throw new RuntimeException("fill both should be resolved to fill width / height");
            }
            case ZOOM_FLAGS_NORMAL:
            default: {
                targetWidth = w;
                targetHeight = h;
                break;
            }
        }

        float scaleWidht = (targetWidth / width);
        float scaleHeight = (targetHeight / height);

        if (LOGV) {
            if (LOGV)
                LogHelper.i(TAG, "of=" + origFlags + "\tf=" + flags + ",\tw=" + w + ",\th=" + h
                        + ",\tbw=" + width + ",\tbh=" + height + ",\ttw=" + targetWidth + ",\tth="
                        + targetHeight + ",\tsw=" + scaleWidht + ",\tsh=" + scaleHeight + ",\tkr="
                        + isZero(scaleHeight / scaleWidht - 1));
        }

        Matrix m = null;
        synchronized (BitmapUtils.class) {
            // small pool of just 1 matrix
            m = sScaleMatrix;
            sScaleMatrix = null;
        }

        if (m == null) {
            m = new Matrix();
        }

        m.setScale(scaleWidht, scaleHeight);

        Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, true);

        synchronized (BitmapUtils.class) {
            // do we need to check for null? why not just assign everytime?
            if (sScaleMatrix == null) {
                sScaleMatrix = m;
            }
        }

        if (recycle && bitmap.isMutable()) {
            bitmap.recycle();
        }

        if (LOGV) {
            if (LOGV)
                LogHelper.i(TAG, "zoom: inWidth=" + width + ", inHeight=" + height + ", outWidth="
                        + result.getWidth() + ", outHeight=" + result.getHeight());
        }

        return result;
    }

    // -------------------------------------------------------------------------

    public static Bitmap drawableToBitmap(Drawable drawable) {
        return drawableToBitmap(drawable, drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight());
    }

    public static Bitmap drawableToBitmap(Drawable drawable, int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height,
                drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
                        : Bitmap.Config.RGB_565);

        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, width, height);
        drawable.draw(canvas);

        return bitmap;
    }

    // -------------------------------------------------------------------------

    public static Bitmap createRoundCornerBitmap(Bitmap bitmap, float roundPx) {
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
                Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(output);

        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return output;
    }

    // -------------------------------------------------------------------------

    // 获得带倒影的图片
    public static Bitmap createReflectionImage(Bitmap bmpSrc) {
        return createReflectionImage(bmpSrc, 0.4f);
    }

    public static Bitmap createReflectionImage(Bitmap bmpSrc, float reflectionRatio) {
        return createReflectionImage(bmpSrc, reflectionRatio, 4);
    }

    public static Bitmap createReflectionImage(Bitmap bmpSrc, float reflectionRatio,
            int reflectionGap) {
        if (reflectionRatio <= 0 || reflectionRatio > 1.0f) {
            return null;
        }

        final int width = bmpSrc.getWidth();
        final int height = bmpSrc.getHeight();
        final int reflectHeight = (int) (height * reflectionRatio);

        Matrix matrix = new Matrix();
        matrix.preScale(1, -1);

        Bitmap reflectionImage = Bitmap.createBitmap(bmpSrc, 0, height - reflectHeight, width,
                reflectHeight, matrix, false);

        Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + reflectHeight),
                Config.ARGB_8888);

        // draw original
        Canvas canvas = new Canvas(bitmapWithReflection);
        canvas.drawBitmap(bmpSrc, 0, 0, null);

        Paint deafalutPaint = new Paint();
        canvas.drawRect(0, height, width, height + reflectionGap, deafalutPaint);

        canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);

        Paint paint = new Paint();
        LinearGradient shader = new LinearGradient(0, bmpSrc.getHeight(), 0,
                bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff,
                TileMode.CLAMP);
        paint.setShader(shader);
        // Set the Transfer mode to be porter duff and destination in
        paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
        // Draw a rectangle using the paint with our linear gradient
        canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint);

        if (LOGV) {
            if (LOGV)
                LogHelper.i(TAG, "reflection: inWidth=" + width + ", inHeight=" + height
                        + ", outWidth=" + bitmapWithReflection.getWidth() + ", outHeight="
                        + bitmapWithReflection.getHeight());
        }

        return bitmapWithReflection;
    }

    // -------------------------------------------------------------------------

    public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
        if (rotation == 0)
            return source;

        int w = source.getWidth();
        int h = source.getHeight();

        Matrix m = new Matrix();
        m.postRotate(rotation);

        Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);

        if (recycle)
            source.recycle();

        return bitmap;
    }

    // -------------------------------------------------------------------------

    private static final int DEFAULT_QUALITY = 90;

    public static byte[] compressToBytes(Bitmap bitmap, CompressFormat format) {
        return compressToBytes(bitmap, format, DEFAULT_QUALITY);
    }

    public static byte[] compressToBytes(Bitmap bitmap, CompressFormat format, int quality) {
        if (bitmap == null || bitmap.isRecycled()) {
            if (LOGV)
                LogHelper.e(TAG, "bad bitmap: " + bitmap, new Exception());
            return null;
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
        bitmap.compress(format, quality, baos);
        return baos.toByteArray();
    }

    public static void compressToFile(Bitmap bitmap, File file, CompressFormat format) {
        compressToFile(bitmap, file, format, DEFAULT_QUALITY);
    }

    public static void compressToFile(Bitmap bitmap, File file, CompressFormat format, int quality) {
        if (bitmap == null || bitmap.isRecycled()) {
            if (LOGV)
                LogHelper.e(TAG, "bad bitmap: " + bitmap, new Exception());
            return;
        }

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            bitmap.compress(format, quality, fos);
        } catch (Exception e) {
            if (LOGV)
                LogHelper.i(TAG, "could not write file" + e);
        } finally {
            Utilities.silentlyClose(fos);
        }
    }

    public static void compressToStream(Bitmap bitmap, OutputStream os, CompressFormat format) {
        compressToStream(bitmap, os, format, DEFAULT_QUALITY);
    }

    public static void compressToStream(Bitmap bitmap, OutputStream os, CompressFormat format,
            int quality) {
        if (bitmap == null || bitmap.isRecycled()) {
            if (LOGV)
                LogHelper.e(TAG, "bad bitmap: " + bitmap, new Exception());
            return;
        }

        try {
            bitmap.compress(format, quality, os);
        } catch (Exception e) {
            if (LOGV)
                LogHelper.i(TAG, "could not write to stream" + e);
        } finally {
        }
    }

    // -------------------------------------------------------------------------

    /**
     * 放大icon，扩充的区域用透明来填充
     */
    public static Bitmap scaleBitmapTransparent(Bitmap icon, int w, int h, boolean recycle) {
        if (w <= 0 || h <= 0) {
            return icon;
        }

        int sourceWidth = icon.getWidth();
        int sourceHeight = icon.getHeight();

        if (sourceWidth >= w || sourceHeight >= h) {
            return scaleBitmap(icon, w, h, recycle);
        }

        final Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas();
        canvas.setBitmap(bitmap);

        float sx = w / (float) sourceWidth;
        float sy = h / (float) sourceHeight;

        if (sx > 1.0 || sy > 1.0) {
            final int left = (w - sourceWidth) / 2;
            final int top = (h - sourceHeight) / 2;
            canvas.drawBitmap(icon, left, top, null);
        } else {
            Matrix matrix = new Matrix();
            matrix.setScale(sx, sy);
            canvas.drawBitmap(icon, matrix, null);
        }
        MissedAPI.Canvas_setBitmap(canvas, null);
        if (recycle) {
            icon.recycle();
        }

        return bitmap;
    }

    /**
     * @param bitmap
     * @param w
     * @param h
     * @param recycle 当确定bitmap可以被recycle时，才设为true
     * @return
     */
    public static Bitmap scaleBitmap(Bitmap bitmap, int w, int h, boolean recycle) {
        return scaleBitmap(bitmap, w, h, Bitmap.Config.ARGB_8888, recycle);
    }

    public static Bitmap scaleBitmap(Bitmap bitmap, int w, int h, Config config, boolean recyle) {
        if (bitmap == null) {
            return null;
        }
        Bitmap newBitmap = Bitmap.createBitmap(w, h, config);
        if (newBitmap == null) {
            return null;
        }
        newBitmap.setDensity(bitmap.getDensity());
        Canvas canvas = new Canvas(newBitmap);
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG));
        canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()), new Rect(
                0, 0, w, h), null);
        if (recyle) {
            bitmap.recycle();
        }
        MissedAPI.Canvas_setBitmap(canvas, null);

        return newBitmap;
    }

    // -------------------------------------------------------------------------

    public static Bitmap loadBitmap(int resId, int maxWidth, int maxHeight) {
        final Context ctx = CleanApplication.getInstance();
        return loadBitmap(ctx, resId, maxWidth, maxHeight);
    }

    public static Bitmap loadBitmap(Context ctx, int resId, int maxWidth, int maxHeight) {
        if (ctx == null) {
            ctx = CleanApplication.getInstance();
        }

        final Resources res = ctx.getResources();

        InputStream is = null;
        try {
            TypedValue tv = new TypedValue();
            is = res.openRawResource(resId, tv);
        } catch (Exception e) {
            if (LOGV)
                LogHelper.e(TAG, "ERROR load from res: " + resId, e);
            return null;
        }

        final int width = maxWidth;
        final int height = maxHeight;
        if (width <= 0 || height <= 0) {
            try {
                return BitmapFactory.decodeStream(is);
            } catch (Throwable tr) {
                if (LOGV)
                    LogHelper.e(TAG, "ERROR load from res: " + resId, tr);
                return null;
            }
        }

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);

        try {
            is.reset();
        } catch (Exception e) {
            if (LOGV)
                LogHelper.i(TAG, "stream could not be reseted, re-create...");
            try {
                Utilities.silentlyClose(is);
                is = res.openRawResource(resId);
            } catch (Exception ee) {
                return null;
            }
        }

        if (options.outWidth == width && options.outHeight == height) {
            return BitmapFactory.decodeStream(is);
        }

        if (options.outHeight == -1 || options.outWidth == -1) {
            if (LOGV)
                LogHelper.i(TAG, "Error get bitmap dimension: " + resId);
            return BitmapFactory.decodeStream(is);
        }

        // Calculate inSampleSize
        options.inSampleSize = Math.max(
                Math.min(options.outHeight / height, options.outWidth / width), 1);
        if (LOGV)
            LogHelper.i(TAG, "sample bitmap: " + resId + ", sample=" + options.inSampleSize
                    + ", bmpWidth=" + options.outWidth + ", bmpHeight=" + options.outHeight
                    + ", width=" + width + ", height=" + height);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        // DO NOT ZOOM HERE
        // caller must zoom the bitmap to expected size, it might has its own
        // aspect / size requirement
        return BitmapFactory.decodeStream(is, null, options);
    }

    public static Bitmap loadBitmap(String path, int maxWidth, int maxHeight) {
        if (LOGV)
            LogHelper.i(TAG, "load bitmap from: " + path + ", maxW=" + maxWidth + ", maxW="
                    + maxHeight);

        if (path != null) {
            if (path.startsWith("content://")) {
                return loadBitmapFromProvider(path, maxWidth, maxHeight);
            } else {
                return loadBitmapFromFile(path, maxWidth, maxHeight);
            }
        }

        return null;
    }

    private static Bitmap loadBitmapFromFile(String path, int screenWidth, int screenHeight) {
        final int width = screenWidth;
        final int height = screenHeight;
        if (width <= 0 || height <= 0) {
            return BitmapFactory.decodeFile(path);
        }

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        if (options.outWidth == width && options.outHeight == height) {
            return BitmapFactory.decodeFile(path);
        }

        if (options.outHeight == -1 || options.outWidth == -1) {
            if (LOGV)
                LogHelper.i(TAG, "Error get bitmap dimension: " + path);
            return BitmapFactory.decodeFile(path);
        }

        // Calculate inSampleSize
        options.inSampleSize = Math.max(
                Math.min(options.outHeight / height, options.outWidth / width), 1);
        if (LOGV)
            LogHelper.i(TAG, "sample bitmap: " + path + ", sample=" + options.inSampleSize
                    + ", bmpWidth=" + options.outWidth + ", bmpHeight=" + options.outHeight
                    + ", width=" + width + ", height=" + height);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        // DO NOT ZOOM HERE
        // caller must zoom the bitmap to expected size, it might has its own
        // aspect / size requirement
        return BitmapFactory.decodeFile(path, options);
    }

    private static Bitmap loadBitmapFromProvider(String path, int screenWidth, int screenHeight) {
        final Context ctx = CleanApplication.getInstance();

        InputStream is = null;
        try {
            is = ctx.getContentResolver().openInputStream(Uri.parse(path));
        } catch (FileNotFoundException e) {
            if (LOGV)
                LogHelper.e(TAG, "ERROR load from provider: " + path, e);
            return null;
        }

        final int width = screenWidth;
        final int height = screenHeight;
        if (width <= 0 || height <= 0) {
            try {
                return BitmapFactory.decodeStream(is);
            } catch (Throwable tr) {
                if (LOGV)
                    LogHelper.e(TAG, "ERROR load from provider: " + path, tr);
                return null;
            }
        }

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);

        try {
            is.reset();
        } catch (Exception e) {
            if (LOGV)
                LogHelper.i(TAG, "stream could not be reseted, re-create...");
            try {
                Utilities.silentlyClose(is);
                is = ctx.getContentResolver().openInputStream(Uri.parse(path));
            } catch (FileNotFoundException fne) {
                if (LOGV)
                    LogHelper.e(TAG, "ERROR load from provider: " + path, fne);
                return null;
            }
        }

        if (options.outWidth == width && options.outHeight == height) {
            return BitmapFactory.decodeStream(is);
        }

        if (options.outHeight == -1 || options.outWidth == -1) {
            if (LOGV)
                LogHelper.i(TAG, "Error get bitmap dimension: " + path);
            return BitmapFactory.decodeStream(is);
        }

        // Calculate inSampleSize
        options.inSampleSize = Math.max(
                Math.min(options.outHeight / height, options.outWidth / width), 1);
        if (LOGV)
            LogHelper.i(TAG, "sample bitmap: " + path + ", sample=" + options.inSampleSize
                    + ", bmpWidth=" + options.outWidth + ", bmpHeight=" + options.outHeight
                    + ", width=" + width + ", height=" + height);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        // DO NOT ZOOM HERE
        // caller must zoom the bitmap to expected size, it might has its own
        // aspect / size requirement
        return BitmapFactory.decodeStream(is, null, options);
    }

    /*private static Bitmap loadBitmapFromNetwork(String url, int maxWidth, int maxHeight) {
        final NetworkManager nm = NetworkManager.getNetworkManager();

        final class localCB extends DefaultNetworkCallback {
            Bitmap bitmap = null;

            @Override
            public void onSuccess(int from, NetworkRequest result) {
                if (from == INetworkCallback.FROM_NETWORK || from == INetworkCallback.FROM_VALID_CACHE) {

                }
            }

            @Override
            public void onFail(int reason, NetworkRequest result) {
                bitmap = null;
            }
        }

        localCB ncb = new localCB();
        nm.performGet(url, ncb, 0, -1, NetworkManager.FLAG_SYNC_OPERATION);

        return ncb.bitmap;
    }*/

    // -------------------------------------------------------------------------

}
